package com.basswold.nasaesa.client;

/**
 * Class it implement building wing construction methods that will
 * be used to construct specific types of building wings.
 * @author Keith
 *
 */
public abstract class BuildingWingFactory {
	
	/**
	 * Currently available modules.
	 */
	protected static ContructionList modules;
	
	/**
	 * Used to circularly index into controlModules array.
	 */
	private int moduleIndex = 0;
	
	private ConstructionModule.ModuleType[] acceptableModules = null;
	
	/**
	 * Used to keep track of wing that is currently in the process
	 * of being constructed.
	 */
	private ConstructionConnectorModule currentConnector = null;
	
	/**
	 * Used to keep track of what direction the wing that is currently
	 * in the process of being constructed is being built towards.
	 */
	private ConstructionModule.CardinalDirection currentDirection = null;
	
	public enum FactoryType {
		ControlWingFactory,
		DormWingFactory,
		FoodWingFactory,
		FoodControlWingFactory
	}
	
	/**
	 * Constructor.
	 * @param moduleList Lists controlling access to modules.
	 */
	BuildingWingFactory(final ContructionList moduleList) {
		modules = moduleList;
		acceptableModules = getAcceptableModules();
		assert acceptableModules != null;
	}
	
	/**
	 * Abstract class getting an array of module types that are acceptable
	 * for a specific concrete wing factory class.
	 * @return An array of module types that are acceptable for the concrete 
	 * 		subclass that extends this class.
	 */
	protected abstract ConstructionModule.ModuleType[] getAcceptableModules();
	
	public abstract FactoryType getType();
	
	/**
	 * Used to obtain modules that are specific to a given type of 
	 * building wing.
	 * @param numberOfWings the number of wings that are being created. Modules
	 * 		will need to be retrieved for each of these wings.
	 * @return An array of modules to add to the wing. Each module will
	 * 		be either of a single module type, or null.
	 */
	protected ConstructionModule[] getModule(final int numberOfWings) {
		ConstructionModule[] returnModules = new ConstructionModule[numberOfWings];
		
		/*
		 * Used to record how many times no modules (of the types
		 * present in the controlModules array) could be retrieved 
		 * from the module list.
		 */
		int emptyModuleCount = 0;
		
		do {			
			// pick a type of module to add. 
			ConstructionModule.ModuleType typeToSelect = 
				acceptableModules[moduleIndex % acceptableModules.length];
			// get the next module in line next time
			moduleIndex++;
			
			// Place one of each of those modules into the array
			for (int i = 0; i < numberOfWings; i++) {
				returnModules[i] = modules.getModuleOfType(typeToSelect);
			}
			
			emptyModuleCount++;
			
			// repeat this if no modules were retrieved and not all of the
			// module types present in the control modules list were tried.
		} while (returnModules[0] == null 
				&& emptyModuleCount < acceptableModules.length);
		
		return returnModules;
	}
	
	/**
	 * Performs initial building operations that are specific to this
	 * type of building wing.
	 * @param direction The direction towards which new modules will be added.
	 * 		This is used to determine in what direction new connector modules
	 * 		will be added. For example, if South, then new connector modules
	 * 		will be added to the south side of the last more recent connector 
	 * 		module.
	 * @return The building wing that has had its construction started.
	 */
	public abstract ConstructionConnectorModule startConstruction(
			ConstructionModule.CardinalDirection direction, boolean airlockIncluded);
	
	/**
	 * Builds an entire building wing.
	 * @param directions The direction towards which new modules will be added.
	 * 		This is used to determine in what direction new connector modules
	 * 		will be added. For example, if South, then new connector modules
	 * 		will be added to the south side of the last more recent connector 
	 * 		module.
	 * @param numWings The number of different wings that should be created.
	 * 		Each wing will try to be as close as possible to the other wings
	 * 		created, but this is not guaranteed depending on the availability
	 * 		of modules.
	 * @param airlockIncluded Whether or not an airlock should be added to the
	 * 		end of this wing, if available.
	 * @return The most recently added connector module that
	 * 		the remainder of the remainder of the wing is connected to.
	 */
	public final ConstructionConnectorModule[] createWings(
			final ConstructionModule.CardinalDirection[] directions,
			final int numWings, boolean airlockIncluded) {
		assert directions.length == numWings;
		
		/**
		 * The most recently added connector module.
		 * As the wing is constructed new connector modules are added
		 * this is the connector module located at the very edge of the wing in
		 * the direction this the wing is being built. 
		 * For example, if the wing is being built in the South direction, this
		 * is the south-most connector module.
		 * This is an array because there is one array element for each of the 
		 * multiple wings being constructed simultaneously.
		 */
		ConstructionConnectorModule[] endConnector = new ConstructionConnectorModule[numWings];
		
		// Perform initial construction steps specific to the type of wing
		for (int i = 0; i < numWings; i++) {
			endConnector[i] = startConstruction(directions[i], airlockIncluded);
		}
		
		// keep adding appropriate modules until no more appropriate modules
		// remain to be added
		ConstructionModule[] modulesToAdd = null;
		int numException;
		do {
			// array of module object to add to wings, each wing gets
			// a single module from this array
			modulesToAdd = this.getModule(numWings);
			
			/* 
			 * Exceptions are thrown when a wing requires another connector
			 * module in order to add the module in the modulesToAdd array.
			 * Even though one wing may not be able to add a module another
			 * wing may still be able to do so. Addition of modules should
			 * not stop until all wings being constructed are unable to add
			 * new modules, which is when each wing throws an exception.
			 * That is why the loop stops when 
			 * numExceptions < modulesToAdd.length
			 */ 
			numException = 0;
			
			if (modulesToAdd[0] != null) {
				// add each item in the array of modules to a wing being constructed
				for (int i = 0; i < numWings; i++) {
					try {
						endConnector[i] = this.addModule(endConnector[i], 
								modulesToAdd[i], directions[i]);
					} catch (Exception ex) {
						numException++;
					}
				}
			}
		} while (modulesToAdd[0] != null && numException < modulesToAdd.length);
		// when 0 index is null there are no more appropriate modules that can
		// be added to these wings
		
		return endConnector;
	}
	
	/**
	 * Begin construction of a particular kind of building wing.
	 * 		Store relevant variables within class.
	 * @param direction The direction towards which to build this wing.
	 * @param airlockIncluded If an airlock to should added to the end of this
	 * 		wing, assuming one is available.
	 */
	public void initialize(ConstructionModule.CardinalDirection direction, 
			boolean airlockIncluded) {
		this.currentConnector = 
			startConstruction(direction, airlockIncluded);
		this.currentDirection = direction;
	}
	
	/**
	 * Attempt to add another module to this wing. What module is added
	 * depends on what kind of wing this is. This method is intended to
	 * allow multiple wings to be constructed in parallel. 
	 * @return True if a new module was added, false if a new module could 
	 * 		not be added. When false is returned this wing is done being
	 * 		constructed.
	 */
	public boolean addNextModule() {
		// We cannot add modules if there is no connector to add to.
		if (currentConnector == null) {
			return false;
		}
		
		boolean returnValue = true;
		
		// keep adding appropriate modules until no more appropriate modules
		// remain to be added
		ConstructionModule[] modulesToAdd = null;

		// array of module object to add to wings, each wing gets
		// a single module from this array
		modulesToAdd = this.getModule(1);
		
		if (modulesToAdd[0] != null) {
			// There are modules to add
			try {
				currentConnector = this.addModule(currentConnector, 
						modulesToAdd[0], currentDirection);
			} catch (Exception ex) {
				// Thrown when there are not enough connector modules. Thus,
				// no more modules can be added to this wing.
				returnValue = false;
			}
		} else {
			// There are no more modules to add
			returnValue = false;
		}
		
		return returnValue;
	}
	
	/**
	 * Complete construction of the wing currently under construction.
	 * @return The last connector module added to the wing.
	 */
	public ConstructionConnectorModule finishConstruction() {
		
		ConstructionConnectorModule connector = currentConnector;
		
		currentConnector = null;
		currentDirection = null;
		
		return connector;
	}
	
	/**
	 * Adds a new module to a partially constructed building wing.
	 * @param buildingWing The partially constructed wing that new modules
	 * 		will be added to.
	 * @param moduleToAdd The module to add to the partially constructed wing.
	 * @param direction The direction which this wing is being constructed in.
	 * 		For example, if this is set to south, then new connectors will be
	 *  	added to the south side of this wing as it grows.
	 *  @return The newly updated building wing. The specific connector module
	 *  	is the connector module that was most recently added.
	 * @throws Exception 
	 */
	protected ConstructionConnectorModule addModule(ConstructionConnectorModule buildingWing, 
			final ConstructionModule moduleToAdd, 
			final ConstructionModule.CardinalDirection direction) throws Exception {
		switch(direction) {
		case NORTH:
			if (buildingWing.hasWestConnection() == false) {
				// open space on west side, place module
				buildingWing.setWestConnection(moduleToAdd);
			} else if (buildingWing.hasEastConnection() == false) {
				// open space on east side, place module
				buildingWing.setEastConnection(moduleToAdd);
			} else {
				// both sides of this connector where modules can be placed
				// already have modules. Add a new connector.
				ConstructionConnectorModule newConnector = 
					(ConstructionConnectorModule) modules.getModuleOfType(
							ConstructionModule.ModuleType.PLAIN);
				if (newConnector == null) {
					// This module cannot be placed, add module back to list
					modules.addModule(moduleToAdd);
					throw new Exception("No connector Modules.");
				} else {
					// there was an available connector
					newConnector.setSouthConnection(buildingWing);
					buildingWing.setNorthConnection(newConnector);
					buildingWing = newConnector;
					buildingWing.setWestConnection(moduleToAdd);
				}
			}
			break;
		case EAST:
			if (buildingWing.hasNorthConnection() == false) {
				// open space on north side, place module
				buildingWing.setNorthConnection(moduleToAdd);
			} else if (buildingWing.hasSouthConnection() == false) {
				// open space on south side, place module
				buildingWing.setSouthConnection(moduleToAdd);
			} else {
				// both sides of this connector where modules can be placed
				// already have modules. Add a new connector.
				ConstructionConnectorModule newConnector = 
					(ConstructionConnectorModule) modules.getModuleOfType(
							ConstructionModule.ModuleType.PLAIN);
				if (newConnector == null) {
					// This module cannot be placed, add module back to list
					modules.addModule(moduleToAdd);
					throw new Exception("No connector Modules.");
				} else {
					// there was an available connector
					newConnector.setWestConnection(buildingWing);
					buildingWing.setEastConnection(newConnector);
					buildingWing = newConnector;
					buildingWing.setNorthConnection(moduleToAdd);
				}
			}
			break;
		case SOUTH:
			if (buildingWing.hasEastConnection() == false) {
				// open space on east side, place module
				buildingWing.setEastConnection(moduleToAdd);
			} else if (buildingWing.hasWestConnection() == false) {
				// open space on west side, place module
				buildingWing.setWestConnection(moduleToAdd);
			} else {
				// both sides of this connector where modules can be placed
				// already have modules. Add a new connector.
				ConstructionConnectorModule newConnector = 
					(ConstructionConnectorModule) modules.getModuleOfType(
							ConstructionModule.ModuleType.PLAIN);
				if (newConnector == null) {
					// This module cannot be placed, add module back to list
					modules.addModule(moduleToAdd);
					throw new Exception("No connector Modules.");
				} else {
					// there was an available connector
					newConnector.setNorthConnection(buildingWing);
					buildingWing.setSouthConnection(newConnector);
					buildingWing = newConnector;
					buildingWing.setEastConnection(moduleToAdd);
				}
			}
			break;
		case WEST:
			if (buildingWing.hasSouthConnection() == false) {
				// open space on south side, place module
				buildingWing.setSouthConnection(moduleToAdd);
			} else if (buildingWing.hasNorthConnection() == false) {
				// open space on north side, place module
				buildingWing.setNorthConnection(moduleToAdd);
			} else {
				// both sides of this connector where modules can be placed
				// already have modules. Add a new connector.
				ConstructionConnectorModule newConnector = 
					(ConstructionConnectorModule) modules.getModuleOfType(
							ConstructionModule.ModuleType.PLAIN);
				if (newConnector == null) {
					// This module cannot be placed, add module back to list
					modules.addModule(moduleToAdd);
					throw new Exception("No connector Modules.");
				} else {
					// there was an available connector
					newConnector.setEastConnection(buildingWing);
					buildingWing.setWestConnection(newConnector);
					buildingWing = newConnector;
					buildingWing.setSouthConnection(moduleToAdd);
				}
			}
			break;
		default:
			break;
		}
		
		return buildingWing;
	}
	
	/**
	 * Attach another module to the "end" of this connector. What direction
	 * 		the "end" direction is depends on the direction which the wing is
	 * 		being built towards.
	 * @param mod The module to attach.
	 * @param direction The direction towards which the building wing is being
	 * 		constructed.
	 */
	public final void setEndConnection(final ConstructionConnectorModule connector,
			final ConstructionModule mod, ConstructionModule.CardinalDirection direction) {
		assert connector != null;
		switch(direction) {
		case NORTH:
			connector.setSouthConnection(mod);
			break;
		case EAST:
			connector.setWestConnection(mod);
			break;
		case SOUTH:
			connector.setNorthConnection(mod);
			break;
		case WEST:
			connector.setEastConnection(mod);
			break;
		}
	}
}
